﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;


/// <summary>
/// An utility  to read and write settings from / to a database
/// </summary>
public static class ConfigurationDatabaseManager
{
    /// <summary>
    /// <add key="TableName" value="LogEntries"/>
    /// </summary>
    /// <returns></returns>
    public static string TableName
    {
        get { return Config.GetValue("ConfigurationDatabaseManager.TableName", "ConfigSettings", false); }
        set { Config.SetValue("ConfigurationDatabaseManager.TableName", value, false); }
    }

    /// <summary>
    /// <add key="IdColumnName" value="Id"/>
    /// </summary>
    /// <returns></returns>
    public static string IdColumnName
    {
        get { return Config.GetValue("ConfigurationDatabaseManager.IdColumnName", "Id", false); }
        set { Config.SetValue("ConfigurationDatabaseManager.IdColumnName", value, false); }
    }

    /// <summary>
    /// <add key="ExistsTable" value="SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'ConfigSettings' "/>
    /// </summary>
    /// <returns></returns>
    public static string ExistsTable
    {
        get { return Config.GetValue("ConfigurationDatabaseManager.ExistsTable", "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'ConfigSettings' ", false); }
        set { Config.SetValue("ConfigurationDatabaseManager.ExistsTable", value, false); }
    }

    /// <summary>
    /// <add key="CreateTable" value="CREATE TABLE ConfigSettings (  Id bigint IDENTITY(1,1) PRIMARY KEY,  Key nvarchar(100),  Value nvarchar(4000) )"/>
    /// </summary>
    /// <returns></returns>
    public static string CreateTable
    {
        get { return Config.GetValue("ConfigurationDatabaseManager.CreateTable", @"CREATE TABLE ConfigSettings (  Id bigint IDENTITY(1,1) PRIMARY KEY,  Code nvarchar(100),  Value nvarchar(4000) )", false); }
        set { Config.SetValue("ConfigurationDatabaseManager.CreateTable", value, false); }
    }


    public static object token = new object();
    private static bool _tableReady = false;
    public static bool TableReady
    {
        get
        {
            lock (token)
            {
                if (_tableReady == false)
                    _tableReady = PrepareTable();
                return _tableReady;
            }
        }
        set { _tableReady = value; }
    }
    /// <summary>
    /// Create table if necessary 
    /// </summary>
    private static bool PrepareTable()
    {
        bool tableOk = false;
        try
        {
            var executeScalar = Data.Database.ExecuteScalar<object>(ExistsTable);
            if (executeScalar == null)
            {
                Log.Warning(String.Format("Table {0} Does not exists", TableName), Log.LogEntry.EntryCategories.DataBase);
                Data.Database.Execute(CreateTable);
                Log.Info(String.Format("Table {0} Created", TableName), Log.LogEntry.EntryCategories.DataBase);
            }
            else
            {
                Log.Debug(String.Format("Table {0} Already Exists", TableName), Log.LogEntry.EntryCategories.DataBase);
            }
            tableOk = true;
        }
        catch (Exception exception)
        {
            Log.Error(exception);
        }
        return tableOk;
    }


    public static object readtoken = new object();
    /// <summary>
    /// Helper to read values from .config files
    /// </summary>
    /// <param name="key">The key name</param>
    /// <returns>The readed value or null if not found</returns>
    public static object ReadValue(string key)
    {
        object value = null;
        ConfigSetting configSetting = null;
        lock (readtoken)
        {
            if (TableReady)
            {
                try
                {
                    configSetting = Data.Database.SingleOrDefault<ConfigSetting>("SELECT * FROM ConfigSettings WHERE Code=@0", key);
                }
                catch (Exception e)
                {
                    Log.Error(new Exception(String.Format("Reading key:{0} ", key), e),Log.LogEntry.EntryCategories.DataBase);
                    throw;
                }
            }
        }
        if(configSetting!=default(ConfigSetting)) value = configSetting.Value;
        return value;
    }

    public static object writetoken = new object();
    /// <summary>
    /// Helper to write values to the database
    /// </summary>
    /// <param name="key">The key name</param>
    /// <param name="value"></param>
    public static void WriteValue(string key, object value)
    {
        lock (writetoken)
        {
            if (TableReady)
            {
                try
                {
                    ConfigSetting configSetting = Data.Database.SingleOrDefault<ConfigSetting>("SELECT * FROM ConfigSettings WHERE Code=@0", key);
                    if (configSetting == null)
                    {
                        configSetting = new ConfigSetting() {Code = key, Value = value.ToString()};
                        Data.Database.Insert(TableName, IdColumnName, configSetting);
                    }
                    else
                    {
                        configSetting.Value = value.ToString();
                        Data.Database.Update(TableName, IdColumnName, configSetting);
                    }
                }
                catch (Exception e)
                {
                    Log.Error(new Exception(String.Format("Writting key:{0} value:{1} ", key, value), e), Log.LogEntry.EntryCategories.DataBase);
                    throw;
                }
            }
        }
    }
    public class ConfigSetting
    {
        public long Id { get; set; }
        public string Code   { get; set; }
        public string Value { get; set; }
    }
}

/// <summary>
/// An utility  to read and write settings from / to the deafulr .config file
/// </summary>
public static class ConfigurationAppFileManager
{
    public static object readtoken = new object();
    /// <summary>
    /// Helper to read values from .config files
    /// </summary>
    /// <param name="key">The key name</param>
    /// <returns>The readed value or null if not found</returns>
    public static object ReadValue(string key)
    {
        object value = null;
        lock (readtoken)
        {
            value = ConfigurationManager.AppSettings[key];
        }
        return value;
    }
    public static object writetoken = new object();
    /// <summary>
    /// Helper to write values to the database
    /// </summary>
    /// <param name="key">The key name</param>
    /// <param name="value"></param>
    public static void WriteValue(string key, object value)
    {
        lock (writetoken)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            config.AppSettings.Settings.Add(key, value.ToString());
            config.Save(ConfigurationSaveMode.Modified);
            ConfigurationManager.RefreshSection("appSettings");
        }
    }
}

/// <summary>
/// Base class to derive configuration classes
/// </summary>
public static class Config
{

    /// <summary>
    /// <add key="Config.ApplicationName" value="AppDomain.CurrentDomain.FriendlyName.Trim().Replace(":", "")"/>
    /// <remarks>This setting does not use database ;-)</remarks>
    /// </summary>
    /// <returns></returns>
    public static string ApplicationName
    {
        get { return Config.GetValue<string>("Config.ApplicationName", AppDomain.CurrentDomain.FriendlyName.Trim().Replace(":", ""), false); }
        set { Config.SetValue("Config.ApplicationName", value, false); }
    }

    /// <summary>
    /// <add key="Config.WorkDirectory" value="Environment.GetFolderPath(folder: Environment.SpecialFolder.MyDocuments) + "\Work\""/>
    /// <remarks>This setting does not use database ;-)</remarks>
    /// </summary>
    /// <returns></returns>
    public static string WorkDirectory
    {
        get { return Config.GetValue<string>("Config.WorkDirectory", Environment.GetFolderPath(folder: Environment.SpecialFolder.MyDocuments) + "\\Work\\", false); }
        set { Config.SetValue("Config.WorkDirectory", value, false); }
    }

    /// <summary>
    /// <add key="Config.UsesDatabase" value="true"/>
    /// <remarks>This setting does not use database ;-)</remarks>
    /// </summary>
    /// <returns></returns>
    public static bool UsesDatabase
    {
        get { return Config.GetValue<bool>("Config.UsesDatabase", true,false); }
        set { Config.SetValue("Config.UsesDatabase", value,false); }
    }

    private static Dictionary<string, object> _configValues;
    /// <summary>
    /// Cache with key value pais of settings
    /// </summary>
    public static Dictionary<string, object> ConfigValues
    {
        get
        {
            if(_configValues==null)_configValues=new Dictionary<string, object>();
            return _configValues;
        }
        set { _configValues = value; }
    }
    
    /// <summary>
    /// Helper to get a value from a key
    /// Uses an dictionary cache for speed
    /// </summary>
    /// <typeparam name="T">Type of the expected value</typeparam>
    /// <param name="key">Name of the key to search</param>
    /// <param name="defaultValue">Provide a default value when not found</param>
    /// <returns>The value associated with the key</returns>
    public static  T GetValue<T>(string key, T defaultValue = default(T), bool usesDatabase=true)
    {
        T value = default(T);
        Type type = typeof (T);
        object valueReaded = null;
        try
        {
            if (ConfigValues.ContainsKey(key))
            {
                valueReaded = ConfigValues[key];
                if (valueReaded != null) value = valueReaded.ConvertTo<T>();
            }
            if ((type.IsClass && value == null) || (type.IsValueType && value.Equals(default(T))))
            {
                valueReaded = ReadValue(key, usesDatabase);
                if (valueReaded != null) value = valueReaded.ConvertTo<T>();
                if ((type.IsClass && value == null) || (type.IsValueType && value.Equals(default(T)))) value = defaultValue;
                if (valueReaded == null) WriteValue(key, value,usesDatabase);
                SetValue(key, value, usesDatabase);
            }
        }
        catch (Exception e)
        {
            value = defaultValue;
            if (SafeLogger != null)
            {
                Exception cfgE = new Exception("Key:" + key, e);
                SafeLogger(cfgE.ToLogString());
            }
        }
        return value;
    }


    
    /// <summary>
    /// Assings a value to a key
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public static void SetValue<T>(string key, T value, bool usesDatabase = true)
    {
        if (ConfigValues.ContainsKey(key))
        {
            ConfigValues[key] = value;
        }
        else
        {
            ConfigValues.Add(key, value);
        }
        
    }

    
    /// <summary>
    /// Helper to read values from .config files
    /// </summary>
    /// <param name="key">The key name</param>
    /// <returns>The readed value or null if not found</returns>
    private static object ReadValue(string key, bool usesDatabase = true)
    {
        object value = null;
        try
        {
            if (usesDatabase)
                value = ConfigurationDatabaseManager.ReadValue(key);
            else
                value = ConfigurationAppFileManager.ReadValue(key);
        }
        catch (Exception ex)
        {
            if (SafeLogger != null) SafeLogger(ex.ToString());
        }
        return value;
    }
    
    private static void WriteValue<T>(string key, T value, bool usesDatabase = true)
    {
        try
        {
            if (usesDatabase)
                ConfigurationDatabaseManager.WriteValue(key, value);
            else
                ConfigurationAppFileManager.WriteValue(key, value);
        }
        catch (Exception ex)
        {
            if (SafeLogger != null) SafeLogger(ex.ToString());
        }
    }

    /// <summary>
    /// A delegate to be used whe something goes wrong with Config
    /// </summary>
    public static Func<string, bool> SafeLogger;
}


